#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <string>
#include <vector>
#include <algorithm>
#include <memory>

#include <QFile>
#include <QTextStream>
#include <QMessageBox>
#include <QTime>
#include <QtDebug>
#include <QFileDialog>

#ifdef ENABLE_PHONON
    #include <phonon/phonon>
#else
    #include <QSound>
#endif

#include "categorydialog.h"
#include "defines.h"
#include "config.h"

#define ABOUT_TITLE "About "APP_NAME_SHORT
#define ABOUT_TEXT APP_NAME_LONG"\n\
This is utility application supposed to create data packages for YASA android application\n\
Version "VERSION_STRING

std::unique_ptr<qyasa::config> config;
QTreeWidgetItem* selectedTreeItem = nullptr;
QListWidgetItem* selectedListItem = nullptr;
const int32_t undefinedId = -1;
int32_t pasteItemId = undefinedId;
int32_t cutCategoryId = undefinedId;
QString projectFileName;


void playSound(const QString& path)
{
#ifdef ENABLE_PHONON
    Phonon::MediaObject* mediaObject = Phonon::createPlayer(Phonon::NoCategory, Phonon::MediaSource(path));
    mediaObject->play();
#else
    QSound::play(path);
#endif
}

void copyFile(std::string& source)
{
    // copy file to same folder as config.xml, update reference to file in item structure

    QFileInfo projFileInfo(projectFileName);

    QString src = source.c_str();
    QFileInfo srcInfo(src);
    if (srcInfo.exists())
    {
        // slash as path separator should be portable enough
        QString newPath = projFileInfo.canonicalPath() + "/" + srcInfo.fileName();
        if (QFile::copy(src, newPath))
        {
            qDebug() << src << "copied to" << newPath;
            source = newPath.toStdString();
        }
        else
        {
            // file could exists, ignore this error
            qWarning() << "failed to copy" << src << "to" << newPath;
        }
    }
}

void MainWindow::reloadTree(QTreeWidget &tree, QTreeWidgetItem *node, const yasa::configuration::ISTCategory &category)
{
    if (node)
    {
        QTreeWidgetItem *subnode = new QTreeWidgetItem(QStringList(category.name.c_str()), category.id);
        node->insertChild(node->childCount(), subnode);
        node = subnode;
    }
    else
    {
        node = new QTreeWidgetItem(QStringList(category.name.c_str()), category.id);
        tree.insertTopLevelItem(tree.topLevelItemCount(), node);
    }

    node->setIcon(0, QIcon(category.imagePath.c_str()));

    for (size_t idx = 0; idx < category.categories.size(); idx++)
    {
        reloadTree(tree, node, category.categories.at(idx));
    }
}

void MainWindow::reloadTree(QTreeWidget &tree)
{
    tree.clear();
    reloadTree(tree, NULL, config->theRoot);
    ui->treeWidget->expandAll();
    configureCategoryActions();
    reloadItems(*ui->listWidget);
    selectedTreeItem = nullptr;
}

void MainWindow::reloadItems(yasa::configuration::ISTCategory& category,
        const std::vector<struct yasa::configuration::ISTItem>& items, QListWidget& list)
{
    // remove all items
    list.clear();

    // prepare vector of item ids
    std::vector<int32_t> itemid;
    std::for_each(items.cbegin(), items.cend(),
            [&](const yasa::configuration::ISTItem& item) {itemid.push_back(item.id);});

    // find intersection between current category item references and all items
    std::sort(category.items.begin(), category.items.end());
    std::sort(itemid.begin(), itemid.end());
    std::vector<int32_t> foundReferences;
    std::set_intersection(category.items.begin(), category.items.end(), itemid.begin(), itemid.end(), std::back_inserter(foundReferences));

    // add intersected items into list
    std::for_each(foundReferences.begin(), foundReferences.end(),
            [&](const int32_t& itemid)
            {
                yasa::configuration::ISTItem* item = config->findItem(itemid);
                QListWidgetItem *lvitem = new QListWidgetItem(&list, item->id);
                lvitem->setText(item->name.c_str());
                lvitem->setToolTip(item->description.c_str());
                lvitem->setIcon(QIcon(item->imagePath.c_str()));
            });
}

void MainWindow::reloadItems(QListWidget& list)
{
    list.clear();

    const int32_t categoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
    yasa::configuration::ISTCategory* category = config->findCategory(categoryId);

    if (!category)
    {
        qCritical() << "cannot find category with id" << categoryId;
        return;
    }

    reloadItems(*category, config->items, list);
    selectedListItem = nullptr;
}

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent), ui(new Ui::MainWindow)
{
    qsrand(QTime::currentTime().msec());

    ui->setupUi(this);

    setupActions();
    config.reset(new qyasa::config(std::bind(copyFile, std::placeholders::_1)));

    ui->treeWidget->setContextMenuPolicy(Qt::CustomContextMenu);
    ui->listWidget->setContextMenuPolicy(Qt::CustomContextMenu);
}

void MainWindow::setupActions()
{
    // categories
    actionAddCategory = new QAction("&Add category", this);
    actionAddCategory->setStatusTip("Add category");
    actionAddCategory->setIcon(QIcon(":/images/AddCategory"));
    connect(actionAddCategory, SIGNAL(triggered()), this, SLOT(addCategoryHandler()));
    this->ui->menuCategory->addAction(actionAddCategory);
    this->ui->mainToolBar->addAction(actionAddCategory);

    actionEditCategory = new QAction("&Edit category", this);
    actionEditCategory->setStatusTip("Edit category");
    actionEditCategory->setIcon(QIcon(":/images/EditCategory"));
    connect(actionEditCategory, SIGNAL(triggered()), this, SLOT(editCategoryHandler()));
    this->ui->menuCategory->addAction(actionEditCategory);
    this->ui->mainToolBar->addAction(actionEditCategory);

    actionDeleteCategory = new QAction("&Delete category", this);
    actionDeleteCategory->setStatusTip("Delete category");
    actionDeleteCategory->setIcon(QIcon(":/images/RemoveCategory"));
    connect(actionDeleteCategory, SIGNAL(triggered()), this, SLOT(deleteCategoryHandler()));
    this->ui->menuCategory->addAction(actionDeleteCategory);
    this->ui->mainToolBar->addAction(actionDeleteCategory);
    this->ui->mainToolBar->addSeparator();

    configureCategoryActions();

    // items
    actionAddItem = new QAction("&Add item", this);
    actionAddItem->setStatusTip("Add item");
    actionAddItem->setIcon(QIcon(":/images/AddItem"));
    connect(actionAddItem, SIGNAL(triggered()), this, SLOT(addItemHandler()));
    this->ui->menuItem->addAction(actionAddItem);
    this->ui->mainToolBar->addAction(actionAddItem);

    actionEditItem = new QAction("&Edit item", this);
    actionEditItem->setStatusTip("Edit item");
    actionEditItem->setIcon(QIcon(":/images/EditItem"));
    connect(actionEditItem, SIGNAL(triggered()), this, SLOT(editItemHandler()));
    this->ui->menuItem->addAction(actionEditItem);
    this->ui->mainToolBar->addAction(actionEditItem);

    actionDeleteItem = new QAction("&Delete item", this);
    actionDeleteItem->setStatusTip("Delete item");
    actionDeleteItem->setIcon(QIcon(":/images/RemoveItem"));
    connect(actionDeleteItem, SIGNAL(triggered()), this, SLOT(deleteItemHandler()));
    this->ui->menuItem->addAction(actionDeleteItem);
    this->ui->menuItem->addSeparator();
    this->ui->mainToolBar->addAction(actionDeleteItem);
    this->ui->mainToolBar->addSeparator();

    actionCutItem = new QAction("&Cut item", this);
    actionCutItem->setStatusTip("Cut item");
    connect(actionCutItem, SIGNAL(triggered()), this, SLOT(cutItemHandler()));
    this->ui->menuItem->addAction(actionCutItem);
    //this->ui->mainToolBar->addAction(actionCutItem);

    actionCopyItem = new QAction("C&opy item", this);
    actionCopyItem->setStatusTip("Copy item");
    connect(actionCopyItem, SIGNAL(triggered()), this, SLOT(copyItemHandler()));
    this->ui->menuItem->addAction(actionCopyItem);
    //this->ui->mainToolBar->addAction(actionCopyItem);

    actionPasteItem = new QAction("&Paste item", this);
    actionPasteItem->setStatusTip("Paste item");
    connect(actionPasteItem, SIGNAL(triggered()), this, SLOT(pasteItemHandler()));
    this->ui->menuItem->addAction(actionPasteItem);
    //this->ui->mainToolBar->addAction(actionPasteItem);

    configureItemActions();

    // menu items
    ui->actionSave_project->setEnabled(false);

    // shortcuts
    //ui->actionExit->setShortcut(tr("ctrl+x"));
    actionCutItem->setShortcut(QKeySequence::Cut);
    actionCopyItem->setShortcut(QKeySequence::Copy);
    actionPasteItem->setShortcut(QKeySequence::Paste);
    ui->actionOpen_project->setShortcut((QKeySequence::Open));
    ui->actionSave_project->setShortcut((QKeySequence::Save));
    ui->actionSave_Project_As->setShortcut((QKeySequence::SaveAs));
}

MainWindow::~MainWindow()
{
    on_actionSave_project_triggered();
    delete ui;
}

void MainWindow::on_treeWidget_customContextMenuRequested(const QPoint &pos)
{
    QMenu menu(this);
    menu.clear();
    menu.addAction(actionAddCategory);
    menu.addAction(actionEditCategory);
    menu.addAction(actionDeleteCategory);

    selectedTreeItem = ui->treeWidget->itemAt(pos);
    configureCategoryActions();

    QPoint rpos;
    menu.popup(rpos);
    menu.exec(QCursor::pos());
}

void MainWindow::addCategoryHandler()
{
    // show dialog
    CategoryDialog d(this);
    d.setWindowTitle("Add category");
    d.exec();

    //check result of dialog
    if (!d.IsAccepted())
    {
        selectedTreeItem = nullptr;
        return;
    }

    // gather data from dialog
    yasa::configuration::ISTCategory cat;
    cat.name = d.getName().toStdString();
    cat.description = d.getDescription().toStdString();
    cat.imagePath = d.getImagePath().toStdString();
    cat.soundPath = d.getSoundPath().toStdString();

    const int32_t parentCategoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
    config->addCategory(cat, parentCategoryId);

    // reload tree
    reloadTree(*ui->treeWidget);
}

void MainWindow::editCategoryHandler()
{
    const int32_t categoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
    yasa::configuration::ISTCategory* category = config->findCategory(categoryId);
    if (!category)
    {
        qCritical() << "could not find category with id" << categoryId;
        selectedTreeItem = nullptr;
        return;
    }

    // push properties to dialog
    CategoryDialog d(this);
    d.setWindowTitle("Edit category");
    d.setName(category->name.c_str());
    d.setDescription(category->description.c_str());
    d.setImagePath(category->imagePath.c_str());
    d.setSoundPath(category->soundPath.c_str());

    // show dialog
    d.exec();

    //check result of dialog
    if (!d.IsAccepted())
    {
        selectedTreeItem = nullptr;
        qDebug() << "canceled";
        return;
    }

    // gather data from dialog
    category->name = d.getName().toStdString();
    category->description = d.getDescription().toStdString();
    category->imagePath = d.getImagePath().toStdString();
    category->soundPath = d.getSoundPath().toStdString();

    // reload tree
    reloadTree(*ui->treeWidget);
}

void MainWindow::deleteCategoryHandler()
{
    const int32_t categoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
    yasa::configuration::ISTCategory* category = config->findCategory(categoryId);
    if (!category)
    {
        qWarning() << "could not find category with id" << categoryId;
        return;
    }

    QMessageBox msg(QMessageBox::Question, "Deleting category", QString("Are you sure you want to delete category "
            + QString(category->name.c_str()) + "?"), QMessageBox::Yes | QMessageBox::No);
    if (QMessageBox::No == msg.exec())
    {
        return;
    }

    config->deleteCategory(categoryId);
    reloadTree(*ui->treeWidget);
}

void MainWindow::addItemHandler()
{
    // show dialog
    CategoryDialog d(this);
    d.setWindowTitle("Add item");
    d.exec();

    //check result of dialog
    if (!d.IsAccepted())
    {
        return;
    }

    // create new item
    yasa::configuration::ISTItem item;
    item.name = d.getName().toStdString();
    item.description = d.getDescription().toStdString();
    item.imagePath = d.getImagePath().toStdString();
    item.soundPath = d.getSoundPath().toStdString();

    const int32_t categoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
    config->addItem(item, categoryId);
    reloadItems(*ui->listWidget);
}

void MainWindow::cutItemHandler()
{
    pasteItemId = selectedListItem ? selectedListItem->type() : undefinedId;
    cutCategoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
}

void MainWindow::copyItemHandler()
{
     pasteItemId = selectedListItem ? selectedListItem->type() : undefinedId;
}

void MainWindow::pasteItemHandler()
{
    const int32_t categoryId = selectedTreeItem ? selectedTreeItem->type() : undefinedId;
    if (config->pasteItem(pasteItemId, categoryId, cutCategoryId))
    {
        reloadItems(*ui->listWidget);
    }

    // reset pasted item
    pasteItemId = undefinedId;
    cutCategoryId = undefinedId;
}

void MainWindow::editItemHandler()
{
    const int32_t itemId = selectedListItem ? selectedListItem->type() : undefinedId;
    yasa::configuration::ISTItem* item = config->findItem(itemId);
    if (!item)
    {
        qWarning() << "could not find item with id" << itemId;
        return;
    }

    // push properties to dialog
    CategoryDialog d(this);
    d.setWindowTitle("Edit item");
    d.setName(item->name.c_str());
    d.setDescription(item->description.c_str());
    d.setImagePath(item->imagePath.c_str());
    d.setSoundPath(item->soundPath.c_str());

    // show dialog
    d.exec();

    //check result of dialog
    if (!d.IsAccepted())
    {
        selectedTreeItem = nullptr;
        qDebug() << "cancelled";
        return;
    }

    // gather data from dialog
    item->name = d.getName().toStdString();
    item->description = d.getDescription().toStdString();
    item->imagePath = d.getImagePath().toStdString();
    item->soundPath = d.getSoundPath().toStdString();

    // reload items
    reloadItems(*ui->listWidget);
}

void MainWindow::deleteItemHandler()
{
    const int32_t itemId = selectedListItem ? selectedListItem->type() : undefinedId;
    const yasa::configuration::ISTItem* item = config->findItem(itemId);
    if (!selectedListItem)
    {
        qWarning() << "cannot find item with id" << itemId;
        return;
    }

    QMessageBox msg(QMessageBox::Question, "Deleting item", "Are you sure you want to delete item "
            + QString(item->name.c_str()) + "?", QMessageBox::Yes | QMessageBox::No);
    if (QMessageBox::No == msg.exec())
    {
        return;
    }

    // remove it
    config->deleteItem(itemId);
    reloadItems(*ui->listWidget);
}

void MainWindow::on_listWidget_customContextMenuRequested(const QPoint &pos)
{
    QMenu menu(this);
    menu.clear();
    menu.addAction(actionAddItem);
    menu.addAction(actionEditItem);
    menu.addAction(actionDeleteItem);
    menu.addSeparator();
    menu.addAction(actionCutItem);
    menu.addAction(actionCopyItem);
    menu.addAction(actionPasteItem);

    selectedListItem = ui->listWidget->itemAt(pos);
    configureItemActions();

    QPoint rpos;
    menu.popup(rpos);
    menu.exec(QCursor::pos());
}

void MainWindow::on_treeWidget_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem */*previous*/)
{
    selectedTreeItem = current;
    reloadItems(*ui->listWidget);
    configureCategoryActions();
    configureItemActions();
}

void MainWindow::configureCategoryActions()
{
    actionEditCategory->setEnabled(nullptr != selectedTreeItem);
    actionDeleteCategory->setEnabled(nullptr != selectedTreeItem);
}

void MainWindow::configureItemActions()
{
    actionEditItem->setEnabled(nullptr != selectedListItem);
    actionDeleteItem->setEnabled(nullptr != selectedListItem);
    actionCutItem->setEnabled(nullptr != selectedListItem);
    actionCopyItem->setEnabled(nullptr != selectedListItem);
    actionPasteItem->setEnabled(undefinedId != pasteItemId);
}

void MainWindow::on_actionExit_triggered()
{
    this->close();
}

void MainWindow::on_actionAbout_triggered()
{
    QMessageBox msg(QMessageBox::Information, ABOUT_TITLE, ABOUT_TEXT, QMessageBox::Ok);
    msg.exec();
}

void MainWindow::on_listWidget_itemChanged(QListWidgetItem *item)
{
    selectedListItem = item;
    configureItemActions();
}

void MainWindow::on_treeWidget_itemChanged(QTreeWidgetItem *item, int /*column*/)
{
    selectedTreeItem = item;
    configureCategoryActions();
}

void MainWindow::on_listWidget_currentItemChanged(QListWidgetItem *current, QListWidgetItem */*previous*/)
{
    selectedListItem = current;
    configureItemActions();
}

void MainWindow::on_actionOpen_project_triggered()
{
    projectFileName = QFileDialog::getOpenFileName(this, tr("Open Project File"), "", tr("config.xml (config.xml)"));
    if (!projectFileName.size())
    {
        return;
    }

    QFile file(projectFileName);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
    {
        return;
    }

    QString configLines;
    QTextStream stream(&file);
    while (!stream.atEnd())
    {
        configLines += stream.readLine();
    }

    config.reset(new qyasa::config(std::bind(copyFile, std::placeholders::_1)));
    config->fromXml(configLines.toStdString());

    this->setGeometry(config->mainWindowRect.x, config->mainWindowRect.y,
                      config->mainWindowRect.width, config->mainWindowRect.height);

    reloadTree(*ui->treeWidget);

    // project opened, set Save action to enabled
    ui->actionSave_project->setEnabled(true);
}

void MainWindow::on_actionSave_project_triggered()
{
    if (!projectFileName.size())
    {
        // try to save only if something were edited
        if (config->items.size() || config->theRoot.categories.size() || config->theRoot.name.size())
        {
            projectFileName = QFileDialog::getSaveFileName(this, tr("Save Project File"), "", tr(
                    "config.xml (config.xml)"));
            if (!projectFileName.size())
            {
                return;
            }
        }
    }
    saveConfig();
}

void MainWindow::saveConfig()
{
    config->mainWindowRect.x = geometry().x();
    config->mainWindowRect.y = geometry().y();
    config->mainWindowRect.width = geometry().width();
    config->mainWindowRect.height = geometry().height();

    QString configData = config->toXml().c_str();

    QFile file(projectFileName);
    if (!file.open(QIODevice::WriteOnly | QIODevice::Text))
    {
        qCritical() << "failed to write config" << projectFileName;
        return;
    }

    QTextStream stream(&file);
    stream << configData << endl;
}

void MainWindow::on_actionSave_Project_As_triggered()
{
    QString path = QFileDialog::getSaveFileName(this, tr("Save Project File As"), "", tr("config.xml (config.xml)"));
    if (path.size())
    {
        projectFileName = path;
    }
    else
    {
        return;
    }

    saveConfig();
}

void MainWindow::on_treeWidget_itemDoubleClicked(QTreeWidgetItem *item, int /*column*/)
{
    yasa::configuration::ISTCategory* category = config->findCategory(item->type());
    if (!category)
    {
        return;
    }

    playSound(category->soundPath.c_str());
}

void MainWindow::on_listWidget_itemDoubleClicked(QListWidgetItem *item)
{
    yasa::configuration::ISTItem* it = config->findItem(item->type());
    if (!it)
    {
        return;
    }
    playSound(it->soundPath.c_str());
}
